Inline technique calls: composition inside a group, an activity step across one - #466
Draft
m2ux wants to merge 20 commits into
Draft
Inline technique calls: composition inside a group, an activity step across one#466m2ux wants to merge 20 commits into
m2ux wants to merge 20 commits into
Conversation
Seed commit anchoring the draft PR for the inline-technique handling work package of epic #397 (protocol structure: alternatives and delegation the server can see). Implementation follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e link partition The grammar module fixes the ten counting terms the inline-reference guard publishes, and classifies a markdown link destination as a technique reference, a resource reference, or neither. Classification keys on the destination's shape rather than on how the author spelled the path. A destination naming a markdown file outside a resources tree is a technique reference, so the same target cannot be a technique under one spelling and a resource under another. Resource claiming delegates to that classifier: previously a dotless op.md was claimed as a resource id while the identical ./op.md was not claimed at all, which made a technique path invisible to the very scan the guard exists to make total. The verb-case term is fixed case-insensitively on measurement rather than by assumption: the corpus carries 238 capitalised Apply against 59 lowercase, and only the case-insensitive reading reproduces the published census of 172 raw occurrences and 135 logical call sites. The module computes no anchor slug. Heading-anchor resolution stays with the resource layer, so the tree holds one slug computation and one grammar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three ancestor-resolution sites take the workflow the callee's file was found in. A cross-workflow or meta-shared callee therefore inherits the container contracts that govern it where it lives. Two of the three sites passed the requested workflow id, so a callee reached by cross-workflow prefix or through the meta fallback composed against the same-named containers of whichever workflow asked for it — silently inheriting the wrong contract. Both had the provenance-correct value in scope on the adjacent line, and the third site already demonstrated the form. The bundle door now reads its source workflow from readTechniqueWithSource rather than discarding it. The four tests covering this all fail against the prior resolution and pass against the new one, including the both-doors parity case, which is what establishes that the bundle door carried the defect too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The corpus submodule carries Atlassian's intra-group calls resolved into their callers and both rule-addressed citations written as dotted addresses, taking container-targeting qualified pairs to zero. Signed-off-by: Mike Clay <mike.clay@shielded.io>
The committed walk snapshots all reproduce at the corpus pin this branch delivers; only the stamp recording which commit they were generated against still named the previous pin, so the freshness check failed while every snapshot passed. Stamping belongs with the commit that moves the submodule. That commit is already published and the no-amend rule holds, so the stamp lands here on its own rather than being folded backwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The published grammar admits `apply`, `via`, `use`, `follow` and `per` as invoking verbs, and matches each on a word boundary at both ends so a verb abutting punctuation counts like any other. Width is a published term rather than an implementation detail, because it decides the guard's coverage as well as its total. Measured at the delivered corpus pin, these five see 88% of the GitNexus cross-group call sites where `apply` alone sees 30%, and widening to nine verbs buys four further points for thirteen more deduplicated pairs. The corpus-wide totals this fixes: 198 logical call sites, 178 deduplicated pairs, 101 caller files, 73 distinct callees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd publishes its coverage A technique file calls another technique from inside its Protocol prose and nothing resolved those calls, so a renamed callee stranded its callers in silence and a call passing fewer arguments than its callee declares read exactly like one passing all of them. The guard makes the class visible: a new call site joins a census rather than an unmeasured remainder. Two classes fail hard, being unambiguous — a destination that names no file, and a qualified call whose operation half names one of the group's rules, which is read rather than applied and belongs at a dotted address. Both are clean at the delivered corpus pin, so no triage file exists; the triage mechanism is a pure function over a finding set, and its absence on disk is the statement that nothing has needed a verdict. Argument conformance and value-named callees are classified rather than failed. A static reading of the name-match convention cannot see a runtime variable bag, so those bins are a disposition worklist behind --worklist and failing on them would have the guard assert what it cannot observe. The census reports coverage beside every total, because the two are separate facts: the total is reproducible at any verb-list width, and a guard reporting clean over a group it never examined must not read as one that did. It admits 198 of 254 link-resolvable references, 78%. Every figure is keyed on syntax, so none of them is evidence about a caller that names an operation in prose — midnight-system-review reaches this corpus's GitNexus operations nine times across three files with no link and no qualified pair anywhere. A retirement decision needs a reading pass, never this number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… escape The deduplicated-pair key joined caller and callee with a literal NUL byte, which makes the file binary to grep and git. It is spelled as a unicode escape now, the form findingKey already uses for the same separator. The header's class list also names the two classes the guard fails, and lists the argument bins and value-named callees separately as the worklist it prints — matching what the code does. Found by check-source-encoding, which is the guard for exactly this and caught it on the sweep. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine corpus guards resolved their root permissively and hand-rolled a clean-or-findings exit, so neither an absent corpus nor a present-but-hollow one had any way to reach exit 2. Each answered "nothing wrong" to a question it could not read, and this package's own worktree is the case that proves why that matters: the corpus submodule was unprovisioned there, and every one of the nine reported clean. measureOrExit is the entry point for a guard that renders its own findings rather than speaking the finding protocol — it resolves the root strictly, runs the collector, and exits 2 with the reason when either step finds the corpus unreachable. runGuard already covered the guards that speak the protocol; the two together mean every corpus guard has a path to exit 2. Both halves are needed and both are exercised. A missing root is caught by requireWorkflowsRoot; a reachable root the guard walks to no effect is caught by assertScanned, each guard naming the surface it actually inspects. The module-scope permissive root stays as the default parameter value, so importing a guard in a test still never throws. Measured after: 30 guards, 0 unable to report unmeasured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ten terms, ten fixture corpora, each read by the real guard so a fixture pins the term as the guard implements it rather than as a test re-implements it. Each fixture is built so the count under the published answer differs from the count under the plausible alternative, which is what makes it fail when the term's meaning changes. A total cannot show that two of its terms measure the same form, and this package measured exactly that: container-target and counting-unit both track the qualified pair. The overlap is asserted rather than described — one case shows the two moving together on a pair, another shows container-target still having an effect where no pair exists, which is why both terms are published instead of one. The census gains rawLinkOccurrences, the pre-collapse count the counting-unit term converts into call sites. Without it that term had no assertable alternative reading, since every other census figure was already post-collapse. Totals asserted at the delivered corpus commit: 198 logical call sites, 178 deduplicated pairs, 101 caller files, 73 distinct callees, 351 raw link occurrences, 254 link-resolvable references, 82 collapsed pairs, 78% coverage, zero unresolved targets and zero findings in either hard class. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `workflow::group::op` and `group::op` spellings name one operation and inherit one contract. The leading prefix addresses a workflow rather than a container, so the ancestor walk starts after it. Measured on `meta::workflow-engine::dispatch-activity` through the step-bound door: 22 rule names under either spelling, where the qualified spelling previously carried 10. The twelve dropped rules were the whole `workflow-engine` container contract, `verify-dispatched-activity` among them. The bundle door resolves the prefix correctly already, so the two doors disagreed on one reference, which SC-1 asserts they cannot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…aches A depth-first walk carries a visited set, delivers each referenced body once, and continues past an edge reaching a body already delivered. Revisit tolerance is categorical rather than empirical: delivery is a reachability problem, so no closure member computes a value another member consumes and a body is complete the first time it arrives. Each call site resolves against the file it was authored in. A composed body carries its ancestors' Initial/Final blocks, whose relative links point out of the ancestor's directory rather than the callee's; the walk therefore reads each technique's own protocol and reaches an ancestor's calls only when that ancestor is itself a closure member. Measured at corpus pin `12400e85`: of 76 container files, 0 carry a technique link in an Initial or Final block and 0 call sites sit there, so the two readings coincide today. A qualified pair delivers its operation and never the group container, in both the two-link and the bare-text spelling. Measured over the corpus, walking every one of 572 technique files as a root: 230 closure members delivered, 110 revisit edges of which 9 are self-loops, 0 unresolved edges. The live fixture behaves as the plan describes — from `verify-index`, one member (`analyze`), one delivery event, and five revisits carrying both the closing edge and the self-loop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rules take the own-versus-inherited partition inputs and outputs already take. A technique's own rules stay under `rules`; the rules its containers impose arrive under `inherited_rules`, each naming the contract that imposes it — the workflow-root contract or the group path. Attribution is the winning definition's, so an inner container overriding an outer one is credited with the rule that governs. That block is the mechanism the design rests on rather than a refinement on top of it. A folded callee is delivered as the operation body without the container bodies around it, so an unattributed merged map is the point at which the callee's obligations stop being distinguishable from the caller's and the caller carries them past the call — the reading PL-1 rejected. The decoration pass now takes a delivery site rather than a step id, because a call site has neither a bound step nor a binding, and states on the block what extent the obligations bind over: scoped to the named call, ending with it, joining no container tree. A bound step carries no such line, its scope being the step. The items are identical either way, which is what makes parity the delivered property. Delivery keys on the operation rather than on the spelling that reached it. `canonicalTechniqueId` names a technique by its home workflow and its path in that tree, so an unqualified `group::op` through the meta fallback, a `workflow::group::op`, and a folded call site's relative link are one ledger key and one body per agent context. Call-site annotations key apart from the body, so a body shared by two call sites still collapses while each site keeps its own scope. Measured on the heaviest closure in the corpus, `meta::workflow-engine::workflow-orchestrator` at 15 members, with each basis stated: operation bodies alone 37,668 characters (5.89% of the 640,000-character budget); the full projection including rules and inherited blocks 146,769 (22.93%); the rules text within that 97,080 summed per member, against 28,801 carried once and 20,348 for the rules the caller does not already hold. The note-versus-items split is what turns the first figure into the second: one container's rules are the same bytes for every member inheriting them, and only the call-scoped note differs. The fan-out measure reads both halves of the partition, since reading one would report a fall in fan-out where the delivered set is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…doors The activity and orchestrator doors carry the bodies of the operations their techniques call inline. The operations bundle is the channel they are charged to: at the orchestrator door it takes no budget parameter and cannot drop content, so a folded body cannot fail to arrive where the core-operations entry compensating for it could not; at the activity door the same channel's serialised size seeds the eager step-technique counter, so a folded body spends budget a step technique would otherwise have had. The closure is computed over a door's whole delivered set rather than one reference at a time. Every delivered body seeds the visited set before the walk starts, so an operation the door already carries is not folded in beside a copy of itself, and an operation two delivered techniques both reach is folded in once. Bodies key by operation and annotations key by call site, so a body shared by two call sites still collapses while every edge stays visible in `folded_call_sites` — a revisit is a real call, and omitting it would hide a call the agent has to make. Collapse scope at the orchestrator door is documented rather than claimed. `get_workflow` takes no agent identity, so its ledger scope is the session's own agent, which several contexts can hold at once; per-technique collapse there is response-local and the cross-call collapse stays whole-bundle. SC-7a's per-technique collapse against a step-bound delivery holds at the activity door, which is dispatched under a worker identity. Measured. The orchestrator list reproduces at exactly twenty entries, seventeen of them operations and three group-prefix rule references. Its folded closure is 4 bodies over 19 further edges that reach bodies the list already delivers, and 0 unresolved. At the meta door that block is 33,055 characters, taking the operations bundle from 80,259 to 113,314 and bootstrap-time fixed content past its stated budget, which moves to 145,000 with the reason recorded at the threshold. The four bodies are `continue-batch`, `finalize-activity`, `variable-binding` and `version-control::push-branch` — none of them named in the twenty-entry list, so each was an operation an orchestrator was told to apply and had no way to read. The activity door's core worker list folds 2 bodies at 17,313 characters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es ride The core-set table carries the orchestrator list at its twenty entries, naming the six the table omitted — `sync-progress-status`, `resolve-harness-operation`, and the four harness files — and says that three of the twenty are group-prefix rule references expanding to rules rather than to bodies, so seventeen name operations. A new section states which door delivers folded bodies, the counter each is charged to, and how far collapse reaches at each: the orchestrator door unbudgeted and collapsing only as a whole bundle for want of an agent identity, the activity door charged to the counter its own eager tally opens and collapsing per technique in the dispatched worker's scope, and the step-bound door delivering none. It also states the delivery key: an operation's canonical identity rather than the spelling that reached it, which is what lets a folded callee collapse against a step-bound delivery of the same operation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An entry existed in these lists whenever a technique was named inside another technique's protocol, because a reference found during resolution reached the agent as prose it could not follow. Both bundle doors now deliver the bodies those references name, on the same operations-bundle channel the entries ride, so an entry whose only reason was standing in for an inline reference is redundant where the closure reaches it. Retirement is per door and verified for simultaneous removal rather than one entry at a time, which would not be sound: the reduced list's closure was measured and every retired entry still arrives as a folded body, with no body lost against the full list. The orchestrator list goes from 20 entries to 12, still delivering 21 bodies; the worker list from 8 to 5, still delivering 6. Bootstrap-time fixed content falls from 137,916 characters to 133,440 across the same delivered set, the saving being the duplicate rule entries that fewer touched techniques no longer produce. Residue is reported rather than removed optimistically, each with the reason folding does not address it. An entry nothing else reaches is where a walk starts rather than somewhere it arrives. A rule reference names no body, so folded delivery never stands in for one. And the four harness adapters stay because `resolve-harness-operation` picks its callee out of the kind-to-file map rather than through a link: the callee is named by a value, no link-keyed traversal reaches it, and this is the one residue with a door still owed. The non-runtime consumer follows the list unchanged: `check-harness-adapter-set` excludes exactly the three retired `harness-compat` refs from the adapter derivation already, so it reads the same four slugs and still holds the two enumerations to agreement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A callee chosen at run time is counted two ways, and both figures are published
with their bases. The link-keyed basis counts a call site whose link destination
carries a `{token}`: the corpus has none, and that zero is a fact about
templated destinations rather than a claim that no callee is ever chosen at run
time. The activity-layer basis counts the shapes that occur — 8 callees supplied
as a step-binding input value, and 4 drawn from the harness kind-to-file table,
12 together at corpus pin `12400e85`.
Neither figure stands in for the other, and no widening reaches the gap between
them. A step binding makes its choice in the activity YAML, so the calling
technique's prose holds nothing to resolve; the harness table's readers apply
whatever the kind resolves to, so the callee is a table row. Both are addressed
in a different plane from a markdown link, so no verb-list or link-grammar width
would ever admit them. Publishing the two side by side is the discipline the
coverage percentage already follows: a reproducible zero over an empty
population must not read as coverage of a population that is not empty.
The table is read once. `check-harness-adapter-set` exports its map-row parse and
the census consumes it, so the two enumerations of one table cannot drift — which
is the property that guard exists to hold.
Leaving the enumeration vacuous carried a concrete residue rather than a
theoretical one: the four harness adapter entries stay listed in the core
orchestrator set for as long as nothing can reach them, which would preserve in
miniature the papering-over that retiring 20 entries to 12 and 8 to 5 has just
removed at scale.
The second half of the criterion is untouched. Closure over the enumerable
harness set is checked by `check-harness-adapter-set`, and it passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lared window get_technique carries the bodies of the operations its technique calls inline, under folded_techniques beside the requested technique. The response is one envelope on every path: the requested technique under technique:, the closure beside it under the same folded_* keys the two bundle doors already emit, so one reading of a folded block serves all three doors rather than each door teaching its own shape. This is the one door that takes a budget, and the asymmetry is the design. The two bundle doors charge folded bodies to an operations bundle that cannot drop content, which is what makes retiring the compensating core-operations entries like-for-like; a budget on either of them would take that property away, so buildFoldedBundle leaves its bound unbounded by default and only this door passes one. The step door answers one fetch at a time, so it sizes the attachment against the window the caller declares. The budget names what it withholds. Declaring context_tokens is what asks for the bodies; without it the response carries the requested technique and states that it carries nothing else, so the absence is read rather than inferred. The requested technique is delivered either way and seeds the tally, so the budget describes the whole response. Bodies attach until the remainder will not hold the next one, and each one left unsent is listed by identity and composed size under folded_deferred. The walk does not stop at the first body that will not fit: closure members carry no execution order between them, so a large body does not deny the small ones behind it. A budget that dropped content silently is the failure this package exists to remove, which is why this one reports instead. Measured at corpus pin 12400e8. 97 techniques have a non-empty step-door closure, 1,451,224 characters of attachment across them, on the basis of summed delivery-event chars — the composed body of each delivered member, excluding the call-site rows and note the serialised block adds. The heaviest is workflow-engine::workflow-orchestrator: 15 bodies over 35 edges, 157,140 characters against its own 13,864. That closure does not fit a 50,000-token window, which leaves 146,136 characters after the requested body — so the deferral report answers a live case rather than a hypothetical one, and fits from 100,000 tokens up. The heaviest figure is cross-derived rather than measured twice. The walk's own accounting gives 15 delivered bodies and 35 edges; an independent grammar scan of the same file set gives 35 resolvable call sites and 15 distinct call targets, with 0 unresolved on both readings. Two readings of one closure, one counting what the walk did and one counting what the text says. The door-and-counter table carries the step door's row, and the two sentences this change falsified — the API table's parameter and response columns, and the protocol specification's account of what get_technique delivers — are corrected with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ivers The stealth-isolation guard scans the delivered closure of every reachable step rather than that step's bound technique alone. A technique whose protocol calls another technique has the callee's body delivered alongside it, so an invocation one call deep is as available to the agent as one written inline — and a scan stopping at the bound technique reports clean over it. Each finding names the member the text sits in and the step that reaches it, so a hit one call deep is as actionable as a direct one. Closures resolve once per operation rather than once per step, which is what keeps the cost a rounding error: 69 distinct closures serve the whole scan. The scope is discovered from the corpus instead of naming one workflow. Every workflow seeding stealth_mode: true is checked, so a stealth workflow joining the corpus is covered without an edit here — a hard-coded id is how a second one goes unchecked with the guard still reporting clean. Nothing in scope exits unmeasured rather than clean. Scanning every workflow was measured and rejected rather than assumed unworkable. The public-write catalog is a stealth catalog: updating a pull request through gh api -X PATCH is this repo's required REST path, correct wherever disclosure is the point and a leak only under a no-disclosure contract. Run across all 16 workflows it reports 11 findings, every one of them a sanctioned operation. The scope that makes the catalog mean what it says is the stealth set. The widening is cross-derived rather than asserted. In remediate-vuln, 29 techniques are reachable only by inline call and never as a bound step, so the old scan could not read them at all. Injecting a gh issue comment into one of those 29 — gitnexus-operations::impact, reached from five different steps — the pre-change guard reports OK and this one reports 5 findings naming each call path. Same corpus, two implementations, opposite verdicts, and the difference is exactly the closure. The delivery benchmark measures a named scenario. A scenario is a delivery shape rather than a bundle of flags, which matters because a comparison is only valid between runs of the same shape: the gate already refuses to fire across context modes and workflows, and the scenario is what lets a third dimension join that check instead of being lost in an unrecorded flag. A fixture recorded before scenarios reads as the solo shape, which is what it is. Every knob a scenario sets stays overridable. The referenced-technique scenario has every step-bound fetch declare a window, so the step door attaches the closure its technique calls inline. The two bundle doors fold unconditionally and are already in every run; this door folds only when asked, so reaching it is what a scenario is for. Measured against solo on the same corpus. Total delivery goes from 1,446,744 to 1,524,585 characters and the step door from 160,416 to 238,257 — 77,841 more, 48.5% of that door and 5.38% of the run. 24 folded bodies over 11 of 23 step-bound fetches, 0 deferred at a 200,000-token window. The other three doors are byte-identical across the two scenarios, which is what attributes the whole delta to the step door rather than leaving it inferred. That 0 deferrals agrees with a reading taken the other way round: a static sweep of the corpus puts the heaviest step-door closure at 157,140 characters and finds it fits from 100,000 tokens up, so a 200,000-token walk should defer nothing, and the walk deferred nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A technique file tells the assistant how to carry out one job, and partway through its instructions it will sometimes say to go and do what another technique file says. This work draws a line through those calls. Where the file being named sits alongside the caller in the same group — a folder of related operations sharing one declared set of inputs — the call stays as it is, because the group is composing itself and nothing outside it can see the arrangement. Where the call reaches out of the group, it becomes a step in an activity, because that is now the only boundary in the system where the values handed over are declared and checked.
There are 223 such calls, 87 of them inside a group and 136 leaving one. The 87 stay. The 136 are the population this work moves, checks, and then keeps out.
🐛 Issue 📐 Engineering
Motivation
The server that hands out technique files treats a sentence naming another file as ordinary text. It does not go and find the file, it does not compare the values being handed over against the ones that file declares it needs, and it does not deliver that file alongside the first. The assistant is given a path buried in the middle of a sentence and works out the rest on its own; where the path resolves to nothing, it either skips the instruction or improvises, and nothing in the run records which. That drift is already in the tree — renaming a technique, or changing the values it needs, reaches its callers silently. Eleven entries in the server's core-operations list exist only to compensate for the missing delivery by hand.
What changed while this was open
Two pieces of work landed on main that move the ground under it.
The first gives an activity a variable contract. An activity now declares the session values it reads and the values it writes, the writes are contributed to the workflow when the activity joins its graph, and a checker walks the graph for a read nothing writes, a write nothing reads, and a read that some path reaches before any write. 117 of the 122 activity files carry a contract and the check is green.
The second moves routing off the activities and into the workflow. An activity names its outcomes and nothing about what follows; the workflow file states where each outcome leads. Route declarations remaining in activity files: zero. That matters here because it changes the price of splitting an activity in two, which used to cascade into every workflow that borrowed it and is now an edit to one workflow file.
Together those two make the activity boundary the one place in the system where a value crossing between two units of work is declared, checked, and provably written before it is read. That is exactly what an inline call has always lacked. So the question stopped being how to make inline calls checkable and became which of them should exist at all.
The measurement that draws the line
Splitting the 223 calls by whether they cross a group boundary produces a result that had not been looked at before:
The sibling half is concentrated in the shared operation groups: the workflow engine's own operations call each other 27 times, the GitHub command-line group 17, the graph-query group 16, the Rust build group 6.
Those 87 must not move, and moving them would be worse than leaving them. An operation in a shared group is bound as a step by many different workflows. Hoisting a call it makes to one of its own siblings means every one of those workflows gains that sibling as a preceding step — the group's internal ordering published into a dozen workflow files, each holding a copy of a sequence it does not own. That is the same fault the routing work was written to remove, and paying it here to buy uniformity would be a poor trade.
The 136 crossings are the opposite case. Each one reaches a contract written somewhere else, with nothing binding the values and nothing checking them; 73 of them hand over a value the caller has no way to supply. Those are the calls the activity boundary can now take, and check.
One number from the original framing does not survive and is worth stating plainly. This was opened against 118 call edges across 554 technique files. That count reproduces under no counting rule that has been tried, varying by 59% across definitions of "a call" that read identically in prose. Acceptance is not keyed to it. It is keyed to the boundary rule, which needs to answer one question about each call — does it leave the group — and not to a precise total.
Changes
Stage one — the boundary rule. The checker compares the group the calling file belongs to against the group the called file belongs to. Staying inside is composition and passes. Leaving is a defect and fails. This replaces ten separate counting terms, each of which existed to bin and price calls precisely enough to deliver them; a boundary question needs none of that, and none of the coverage caveats that came with it. A call written in prose with no link still evades the check, as it always did, and the check says so rather than reporting clean over ground it never read.
Stage two — repair the crossings inside the groups. 41 sibling calls hand over a value the caller cannot supply. Each is fixed inside its own group, by adding the value to the group's declared inputs or to the calling operation's own signature. No consuming workflow is involved.
Stage three — move the crossings out. The 136 calls that leave a group become activity steps. The value each one carried becomes a variable the activity declares, so the load checks that something writes it and that the write precedes the read on every path. This is batched by group, largest first, and each batch is verified by a check that is already green on main.
Stage four — retire the delivery machinery. The walk that carried callee bodies to the caller, the three doors it rode on, the ledger entry that collapsed a delivered callee against a scheduled one, the block that attributed a callee's inherited rules to the calling site, and the two-plane counting that reported the same population on two bases — all of it existed to make a crossing work in place. A crossing that becomes a step does not need it. What is kept is the part that finds a link in a protocol and the part that compares two group names.
Stage five — the written authorities. The design canon, the construct inventory and the addressing specification currently disagree with each other and with the running server about whether these calls are legitimate at all. They are brought into agreement on the boundary rule: a call to a sibling is how a group composes itself, a call that leaves the group is an activity step.
Cross-workflow ancestry stays as delivered. A called technique takes its container contracts from its own home tree, applied identically at every resolution site — that is correct whether the call is delivered inline or bound at a step.
Core operations. The eleven hand-maintained entries compensating for the missing delivery are settled by the stage that serves each one: an entry standing in for a crossing retires when that crossing becomes a step, an entry standing in for a sibling call retires against the group that owns it, and an entry still unserved stays and is reported.
Acceptance
Not in this change
The obligation to surface risk before an edit. One called operation requires its caller to put a high or critical risk finding to the user before proceeding. It crosses a group boundary and it depends on sitting next to the edit instruction, so as a separate step it has nowhere to live. It needs either a gate the server enforces or a rule on the group container, and that decision is open.
Composite activities, and an activity step whose operation the workflow supplies. These are what would let the repeated pre-check-and-post-check shape be declared once instead of copied per operation, and would give a value hoisted out of a protocol a scope narrower than the whole session. Both are named as unblocked by the routing work and neither is filed. They are the reason the crossings move as ordinary steps here rather than as a reusable wrapper.
Replacing an operation's prose with a typed tool call. Withdrawn before any file was edited, and tracked separately: the acceptance clause and the group's own rule requiring its operations be used cannot both hold.
📌 Submission Checklist
🔱 Fork Strategy
🗹 TODO before merging